Drawing on a button click

Jan 17, 2009 at 7:06am
I need some help. Can you please tell me how to draw in the window of a program after the user clicks a button in the window. I want to have a button that draws a few lines in the program's window when clicked.

Please help me.
Last edited on Jan 21, 2009 at 7:19am
Jan 17, 2009 at 8:45am
I've done & commented this example code for you - I hope it helps, the functions used are:
http://msdn.microsoft.com/en-us/library/ms534247(VS.85).aspx - MoveToEx
http://msdn.microsoft.com/en-us/library/aa453296.aspx - LineTo
http://msdn.microsoft.com/en-us/library/ms632680(VS.85).aspx - CreateWindowEx
http://msdn.microsoft.com/en-us/library/ms632659(VS.85).aspx - LOWORD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <windows.h>

#define BUTTON1 3141
#define BUTTON1_TEXT "Draw lines"

//  Make the class name into a global variable
char szClassName[] = "WindowsApp";
// This is the handle for our window - global
HWND hwnd;
// This is the handle for our button - global
HWND button1;
// This is the handle to the device context for hwnd, we will set this value with GetDC in WinMain
HDC hdc;
//  Declare Windows procedure
//  This function is called by the Windows function DispatchMessage()

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  // handle the messages
    {
		case WM_CREATE: // when the window is created
		   break;
		case WM_COMMAND: // we've received a command
		   if(LOWORD(wParam)==BUTTON1) // is it our button?
		   {
			 // draw some lines
			 MoveToEx(hdc,100,100,0); // move the drawing position to 100, 100
			 LineTo(hdc,150,150); // draw a line from the drawing position to 150, 150
			 MoveToEx(hdc,200,200,0); // move the drawing position to 200, 200
			 LineTo(hdc,300,200); // draw a line from the drawing position to 300, 200
		   }
		   break;
        case WM_DESTROY:
            PostQuitMessage (0);       // send a WM_QUIT to the message queue
            break;
        default:                      // for messages that we don't deal with
            return DefWindowProc (hWnd, message, wParam, lParam);
    }

    return 0;
}


int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nCmdShow)

{
    MSG messages;            // Here messages to the application are saved
    WNDCLASSEX wc  ;        // Data structure for the windowclass

    // The Window structure
    wc.hInstance = hThisInstance;
    wc.lpszClassName = szClassName;
    wc.lpfnWndProc = WindowProcedure;      // This function is called by windows
    wc.style = CS_DBLCLKS;                 // Catch double-clicks
    wc.cbSize = sizeof (WNDCLASSEX);

    // Use default icon and mouse-pointer
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.lpszMenuName = NULL;                 // No menu
    wc.cbClsExtra = 0;                      // No extra bytes after the window class
    wc.cbWndExtra = 0;                      // structure or the window instance
    // Use Windows's default colour as the background of the window
    wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    // Register the window class, and if it fails quit the program
    if(!RegisterClassEx (&wc)){return 0;}

    // The class is registered, let's create the program
    hwnd = CreateWindowEx (
           0,                   // Extended possibilites for variation
           szClassName,         // Classname
           "Windows App",       // Title Text
           WS_OVERLAPPEDWINDOW, // default window
           CW_USEDEFAULT,       // Windows decides the position
           CW_USEDEFAULT,       // where the window ends up on the screen
           400,                 // The program's width
           300,                 // and height in pixels
           HWND_DESKTOP,        // The window is a child-window to desktop
           NULL,                // No menu
           hThisInstance,       // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Make the window visible on the screen
    ShowWindow (hwnd, nCmdShow);
	hdc = GetDC(hwnd); // this is setting hdc to hwnd's hdc - this is used for drawing
	// make our button
	button1 = CreateWindowEx (
           0,                   // Extended possibilites for variation
           "BUTTON",            // Classname
           BUTTON1_TEXT,        // Button Text
           WS_CHILD|WS_VISIBLE, // Child to the window, visible
           5,                   // X co-ordinate
           5,                   // Y co-ordinate
           80,                  // The button's width
           18,                  // and height in pixels
           hwnd,                // The button is a child to hwnd - our window
           (HMENU)BUTTON1,      // This value is sent to our WindowProcedure function when button is clicked
           NULL,                // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Run the message loop. It will run until GetMessage() returns 0
    while (GetMessage (&messages, NULL, 0, 0))
    {
        // Translate virtual-key messages into character messages
        TranslateMessage(&messages);
        // Send message to WindowProcedure
        DispatchMessage(&messages);
    }

    // The program return-value is 0 - The value that PostQuitMessage() gave
    return messages.wParam;
}
Jan 17, 2009 at 1:23pm
Above sample is wrong.
You must always draw on WM_PAINT
Jan 17, 2009 at 2:51pm
I'm going to sound like a jerk for saying this but the thread title "Windows Problem" is stating the obvious when you're posting it in the windows board. Perhaps you could choose a more meaningful subject line next time?
Jan 17, 2009 at 6:06pm
@Malachi
I really don't think so, because that I can't draw is a problem. And it is associated with windows, so I think my title is ok as it says I got a problem with windows.

@chris
I already read those MSDN articles, but they didn't seem to help me.
I will try your code, and I hope it works
Jan 17, 2009 at 6:30pm
As george135 said, you have to draw on WM_PAINT message so that what you draw won't be deleted on the next redraw.

I would modify the window procedure like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   static bool draw = false;

   switch (message)
   {
      case WM_COMMAND:
         if(LOWORD(wParam)==BUTTON1)
         {
            draw = true;
            RedrawWindow(hwnd,0,0,RDW_INVALIDATE);
         }
         break;
      case WM_DESTROY:
         PostQuitMessage (0);
         break;
      case WM_PAINT:
         if (draw)
         {
            MoveToEx(hdc,100,100,0);
            LineTo(hdc,150,150);
            MoveToEx(hdc,200,200,0);
            LineTo(hdc,300,200); 
         }
   }

   return DefWindowProc (hWnd, message, wParam, lParam);
}



A better title would be "Drawing After Button Click" as "Windows Problem" is too generic
Last edited on Jan 17, 2009 at 6:35pm
Jan 18, 2009 at 7:07am
@Bazzy
I also think your suggestion for my title is better. I am changing my title.
I will try your code and see which one is better.
Jan 18, 2009 at 9:09am
To george135 & to Bazzy; although WM_PAINT will keep it always there, I am pretty sure (& it does for me anyway) it takes almost all the CPU it can & causes (for me) at least 95% CPU usage. In my opinion, it is better just find the cases where it would need to be redrawn (such as sizing the window or the window getting focus/loosing focus) & then put the code into there.
So maybe create a global variable if you need to redraw, then have your cases where it needs to redraw & your button click to activate this. I have used this code below & it works well for me, without the CPU usage of calling it on WM_PAINT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <windows.h>

#define BUTTON1 3141
#define BUTTON1_TEXT "Draw lines"

//  Make the class name into a global variable
char szClassName[] = "WindowsApp";
// This is the handle for our window - global
HWND hwnd;
// This is the handle for our button - global
HWND button1;
// This is the handle to the device context for hwnd, we will set this value with GetDC in WinMain
HDC hdc;
//  Declare Windows procedure
//  This function is called by the Windows function DispatchMessage()

bool DrawLines=false; // our variable for if we need to draw

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  // handle the messages
    {

		case WM_SIZE: // for resizing
		case WM_ACTIVATE: // works for focus changes & minimises
		if(DrawLines)
		{
		MoveToEx(hdc,100,100,0); // move the drawing position to 100, 100
		LineTo(hdc,150,150); // draw a line from the drawing position to 150, 150
		MoveToEx(hdc,200,200,0); // move the drawing position to 200, 200
		LineTo(hdc,300,200); // draw a line from the drawing position to 300, 200
		}
		break;
		case WM_COMMAND: // we've received a command
		   // is it our button?
		   if(LOWORD(wParam)==BUTTON1)
		   {
		   DrawLines=true; // set the DrawLines variable to true
		   SendMessage(hwnd,WM_SIZE,0,0); // send the message to SIZE just to redraw
		   }
		   break;
        case WM_DESTROY:
            PostQuitMessage (0);       // send a WM_QUIT to the message queue
            break;
        default:                      // for messages that we don't deal with
            return DefWindowProc (hWnd, message, wParam, lParam);
    }

    return 0;
}


int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nCmdShow)

{
    MSG messages;            // Here messages to the application are saved
    WNDCLASSEX wc  ;        // Data structure for the windowclass

    // The Window structure
    wc.hInstance = hThisInstance;
    wc.lpszClassName = szClassName;
    wc.lpfnWndProc = WindowProcedure;      // This function is called by windows
    wc.style = CS_DBLCLKS;                 // Catch double-clicks
    wc.cbSize = sizeof (WNDCLASSEX);

    // Use default icon and mouse-pointer
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.lpszMenuName = NULL;                 // No menu
    wc.cbClsExtra = 0;                      // No extra bytes after the window class
    wc.cbWndExtra = 0;                      // structure or the window instance
    // Use Windows's default colour as the background of the window
    wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    // Register the window class, and if it fails quit the program
    if(!RegisterClassEx (&wc)){return 0;}

    // The class is registered, let's create the program
    hwnd = CreateWindowEx (
           0,                   // Extended possibilites for variation
           szClassName,         // Classname
           "Windows App",       // Title Text
           WS_OVERLAPPEDWINDOW, // default window
           CW_USEDEFAULT,       // Windows decides the position
           CW_USEDEFAULT,       // where the window ends up on the screen
           400,                 // The program's width
           300,                 // and height in pixels
           HWND_DESKTOP,        // The window is a child-window to desktop
           NULL,                // No menu
           hThisInstance,       // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Make the window visible on the screen
    ShowWindow (hwnd, nCmdShow);
	hdc = GetDC(hwnd); // this is setting hdc to hwnd's hdc - this is used for drawing
	// make our button
	button1 = CreateWindowEx (
           0,                   // Extended possibilites for variation
           "BUTTON",            // Classname
           BUTTON1_TEXT,        // Button Text
           WS_CHILD|WS_VISIBLE, // Child to the window, visible
           5,                   // X co-ordinate
           5,                   // Y co-ordinate
           80,                  // The button's width
           18,                  // and height in pixels
           hwnd,                // The button is a child to hwnd - our window
           (HMENU)BUTTON1,      // This value is sent to our WindowProcedure function when button is clicked
           NULL,                // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Run the message loop. It will run until GetMessage() returns 0
    while (GetMessage (&messages, NULL, 0, 0))
    {
        // Translate virtual-key messages into character messages
        TranslateMessage(&messages);
        // Send message to WindowProcedure
        DispatchMessage(&messages);
    }

    // The program return-value is 0 - The value that PostQuitMessage() gave
    return messages.wParam;
}



(also as a note, I forgot to delete in my code, in WindowProcedure, the WM_CREATE case statement - that's not necessary in my code a few posts ago).
Last edited on Jan 18, 2009 at 9:10am
Jan 18, 2009 at 10:47am
closed account (z05DSL3A)
Read: http://msdn.microsoft.com/en-us/library/ms534870(VS.85).aspx
Jan 19, 2009 at 3:03pm
@chris
I alredy tryed your code, but my compiler gives some kind of strange error that says:

1>d:\data\1\tajjada_projects\tfifview\tfifviewdlg.cpp(291) : error C2440: '=' : cannot convert from 'CDC *' to 'HDC'
1> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast


Please help me solve that error.
Jan 19, 2009 at 3:40pm
closed account (z05DSL3A)
Can you give more info on the project type, IDE/compiler?

I'm guessing that you have an MFC project (of some description) from the CDC* reference.
Last edited on Jan 19, 2009 at 3:54pm
Jan 20, 2009 at 7:23am
I am using VC++ 2008 and I have an MFC Project.
Jan 20, 2009 at 9:14am
closed account (z05DSL3A)
It that case you put your drawing code on the OnPaint() method of the dialog class.

It will look something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void CTestMfcDlgDlg::OnPaint()
{
    (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}


In the else part of the if construct is where you would put your code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void CTestMfcDlgDlg::OnPaint()
{
    (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CPaintDC dc(this); // device context for painting

        CRect rcClient; 
        GetClientRect(rcClient);
        
        CPoint upperLeft,lowerRight;
        upperLeft = rcClient.TopLeft();
        lowerRight = rcClient.BottomRight();
        
        CRect recGraph(upperLeft.x,upperLeft.y,lowerRight.x,lowerRight.y);
        CPen penBlack(PS_SOLID,2,RGB(0,0,0));
        dc.SelectObject(&penBlack);
        CBrush brGreen(RGB(92,200,92));
        dc.SelectObject(&brGreen);
        recGraph.DeflateRect(5,5);
        dc.RoundRect(recGraph,CPoint(15,15));

        CDialog::OnPaint();
    }
}




Hope that helps
Jan 21, 2009 at 7:15am
But is that the code for drawing only on the click of a button or my drawings are always going to stay there?
Jan 21, 2009 at 8:39am
closed account (z05DSL3A)
No, this is just the correct place to put your drawing code. As Bazzy said, You would have a variable in your view class such as

static bool draw = false;

You can then wrap your drawing code in an if clause:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CTestMfcDlgDlg::OnPaint()
{
    (IsIconic())
    {
        ...
    }
    else
    {
        if(draw)
        {        
            CPaintDC dc(this); // device context for painting
            ...
        }
        CDialog::OnPaint();
    }
}


Then finally your event handler for the button click would be somthing like:

1
2
3
4
5
6
7
8
void CTestMfcDlgDlg::OnBnClickedButton1()
{
    // TODO: Add your control notification handler code here

    draw = !draw;  //Toggle drawing on button click
    this->Invalidate(true); 

}



Topic archived. No new replies allowed.